/* *****************************************************************************************************
 * Copyright 2009. Dbwebx Group
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 * *****************************************************************************************************
 */

package org.dbwebx.mojo.flex;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;

import org.codehaus.classworlds.ClassRealm;
import org.codehaus.classworlds.ClassWorld;
import org.codehaus.classworlds.DuplicateRealmException;

import org.dbwebx.mojo.flex.generator.AS3Configuration;
import org.dbwebx.mojo.flex.generator.ClassesResolver;
import org.dbwebx.mojo.flex.generator.Gas3GroovyTransformer;
import org.dbwebx.mojo.flex.generator.Gas3Listener;
import org.dbwebx.mojo.flex.generator.GeneratorHelper;

import org.granite.generator.Generator;
import org.granite.generator.Output;
import org.granite.generator.as3.JavaAs3Input;

import java.io.File;
import java.io.IOException;

import java.net.MalformedURLException;
import java.net.URL;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;


/**
 * This goal generate actionscript 3 services code based on Java services
 * classes. <br/>
 * It does uses Granite GAS3.
 *
 * @author Izzet_Mustafayev
 * @since 1.0
 * @goal generate
 * @phase generate-sources
 */
public class GeneratorMojo extends AbstractMojo {
    /**
     * The maven project.
     *
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    protected MavenProject project;

    /**
     * @component
     */
    protected ArtifactResolver resolver;

    /**
     * Local repository to be used by the plugin to resolve dependencies.
     *
     * @parameter expression="${localRepository}"
     */
    protected ArtifactRepository localRepository;

    /**
     * @component
     */
    protected ArtifactMetadataSource artifactMetadataSource;

    /**
     * @component
     */
    protected MavenProjectBuilder mavenProjectBuilder;

    /**
     * The Maven Session Object
     *
     * @parameter expression="${session}"
     * @required
     * @readonly
     */
    protected MavenSession session;

    /**
     * @component
     */
    protected ArtifactFactory artifactFactory;

    /**
     * List of remote repositories to be used by the plugin to resolve
     * dependencies.
     *
     * @parameter expression="${project.remoteArtifactRepositories}"
     */
    @SuppressWarnings("unchecked")
    protected List remoteRepositories;

    /**
     * File to generate as3 file. If not defined assumes all classes must be
     * included
     *
     * @parameter
     */
    private String[] includeJavaClasses;

    /**
     * File to exclude from as3 generation. If not defined, assumes no
     * exclusions
     *
     * @parameter
     */
    private String[] excludeJavaClasses;

    /**
     * @parameter
     */
    private String outputPackageName;

    /**
     * Classes to generate AS3 based on.
     */
    private Map<String, File> classes;

    /**
     * @parameter
     *            default-value="${project.build.directory}/generated-sources/flexmojos"
     */
    private File baseOutputDirectory;

    /**
     * @parameter default-value="${project.build.sourceDirectory}"
     */
    private File outputDirectory;

    /**
     * internal properties
     */
    private Gas3Listener listener;

    /**
     * File to include as output from as3 generation. If not defined, assumes
     * all classes are included in the output
     *
     * @parameter
     */
    private String[] outputClasses;

    /**
     * @parameter
     */
    private String delegateTemplate;
    private Gas3GroovyTransformer transformer;

    public void execute() throws MojoExecutionException {
        setUp();
        getLog().info("Enter to Services generation phase.");

        List<File> jarDependencies = getJarDependencies();

        if (jarDependencies.isEmpty()) {
            getLog().warn("No jar dependencies found.");
        }

        if (!jarDependencies.isEmpty()) {
            processExecution(jarDependencies);
        }
    }

    private void processExecution(List<File> jarDependencies)
        throws MojoExecutionException {
        try {
            classes = new ClassesResolver(includeJavaClasses, excludeJavaClasses).resolveClasses(jarDependencies);
        } catch (IOException e) {
            throw new MojoExecutionException("Error on classes resolve", e);
        }

        ClassLoader classLoader = setUpClassLoader(jarDependencies);

        Generator generator = getGenerator(classLoader);
        int count = 0;

        for (Map.Entry<String, File> classEntry : classes.entrySet()) {
            Class<?> clazz = null;

            try {
                clazz = classLoader.loadClass(classEntry.getKey());

                JavaAs3Input input = new JavaAs3Input(clazz, classEntry.getValue());

                for (Output<?> output : generator.generate(input)) {
                    if (output.isOutdated()) {
                        count++;
                    }
                }
            } catch (Exception e) {
                getLog().warn("Could not generate AS3 beans for: '" + clazz + "'", e);
            }
        }

        getLog().info(count + " files generated.");
    }

    private ClassLoader setUpClassLoader(List<File> jarDependencies)
        throws MojoExecutionException {
        ClassLoader classLoader = null;

        try {
            // create a new classloading space
            ClassWorld world = new ClassWorld();

            // use the existing ContextClassLoader in a realm of the
            // classloading space
            ClassRealm realm =
                world.newRealm("plugin.fxgenerator.generator", Thread.currentThread().getContextClassLoader());

            // create another realm for just the dependency jars and make
            // sure it is in a child-parent relationship with the current
            // ContextClassLoader
            ClassRealm gas3GeneratorRealm = realm.createChildRealm("gas3Generator");

            // add all the jars to the new child realm
            for (URL url : GeneratorHelper.getUrls(jarDependencies))
                gas3GeneratorRealm.addConstituent(url);

            classLoader = gas3GeneratorRealm.getClassLoader();
            Thread.currentThread().setContextClassLoader(gas3GeneratorRealm.getClassLoader());
        } catch (MalformedURLException e) {
            throw new MojoExecutionException("Unable to get dependency URL", e);
        } catch (DuplicateRealmException e) {
            throw new MojoExecutionException("Unable to create new class loading realm", e);
        }

        return classLoader;
    }

    private Generator getGenerator(ClassLoader loader)
        throws MojoExecutionException {
        this.listener = new Gas3Listener(getLog());
        getLog().info("Template path: " + delegateTemplate);

        AS3Configuration configuration = new AS3Configuration(baseOutputDirectory, outputDirectory, classes, loader);
        configuration.setupTemplateUris(delegateTemplate);

        Generator generator = new Generator(configuration);
        transformer = new Gas3GroovyTransformer(configuration, this.listener, outputClasses, outputPackageName);
        generator.add(transformer);

        return generator;
    }

    private void setUp() throws MojoExecutionException {
        if (includeJavaClasses == null) {
            includeJavaClasses = new String[] {"*"};
        }

        if (!outputDirectory.exists()) {
            outputDirectory.mkdirs();
        }

        String outputPath = outputDirectory.getAbsolutePath();

        if (!project.getCompileSourceRoots().contains(outputPath)) {
            project.addCompileSourceRoot(outputPath);
        }

        if (!baseOutputDirectory.exists()) {
            baseOutputDirectory.mkdirs();
        }

        String baseOutputPath = baseOutputDirectory.getAbsolutePath();

        if (!project.getCompileSourceRoots().contains(baseOutputPath)) {
            project.addCompileSourceRoot(baseOutputPath);
        }
    }

    @SuppressWarnings("unchecked")
    private List<File> getJarDependencies() {
        List<File> jarDependencies = new ArrayList<File>();
        final Collection<Dependency> dependencies;
        dependencies = project.getDependencies();

        for (Dependency dependency : dependencies) {
            if ("jar".equals(dependency.getType())) {
                // create artifact from dependency
                Artifact artifact =
                    artifactFactory.createArtifact(
                        dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(),
                        dependency.getScope(), dependency.getType());

                try {
                    resolver.resolveAlways(artifact, remoteRepositories, localRepository);
                } catch (AbstractArtifactResolutionException e) {
                    getLog().warn("Dependency file not found: " + dependency);
                    getLog().debug(e);

                    continue;
                }

                File file = artifact.getFile();

                if ((file != null) && file.exists()) {
                    jarDependencies.add(file);
                }
            }
        }

        return jarDependencies;
    }
}
